Skip to content

feat(cli): sync policies, diff/sync, and unified agent skill#752

Merged
HugoRCD merged 6 commits into
mainfrom
feat/sync-policies-unified-skill
May 30, 2026
Merged

feat(cli): sync policies, diff/sync, and unified agent skill#752
HugoRCD merged 6 commits into
mainfrom
feat/sync-policies-unified-skill

Conversation

@HugoRCD
Copy link
Copy Markdown
Owner

@HugoRCD HugoRCD commented May 30, 2026

Summary

  • Add sync policies in shelve.json (sourceOfTruth, onPushConflict, pullMode, allowPush/allowPull, protectedEnvironments) with shared resolution in @types and CLI enforcement on push/pull.
  • New commands shelve diff (compare local vs Shelve, no writes) and shelve sync (apply policy; --dry-run supported).
  • Server-side syncPolicy on projects (migration 0007), API push guard (ENV_PROTECTED), and protected environments in project Settings.
  • Single agent skill shelve: merge former shelve-app into platform.md + sync-policies.md; remove duplicate published skill.

Test plan

  • pnpm exec vitest run in packages/cli
  • pnpm typecheck (CLI + app as needed)
  • shelve diff --env development against a configured project
  • shelve push blocked when protectedEnvironments includes target env
  • Project Settings → protected environments blocks API variable create
  • pnpm build:lp and verify /.well-known/skills/index.json lists only shelve
  • npx skills add https://shelve.cloud (or preview URL) installs one skill

Summary by CodeRabbit

  • New Features

    • Sync policies for managing push/pull conflicts and protected environments
    • New commands: shelve diff (with JSON output) and shelve sync (policy-driven)
    • shelve push/pull now honor sync policies, pull-mode (merge/replace), and show skipped/conflict keys
    • CLI confirmation controls for automated/non-interactive workflows
    • Shelve agent skill consolidated into a single “shelve” skill
  • UI

    • Project settings: manage protected environments via a sync policy input
  • Documentation

    • Extensive sync-policies docs, command references, and updated troubleshooting/error codes

Review Change Stack

Add configurable source-of-truth rules in shelve.json (push/pull guards,
onPushConflict, pull merge mode) with shelve diff and shelve sync. Enforce
protected environments server-side via project syncPolicy. Merge shelve-app
into a single comprehensive shelve agent skill.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
shelve-app Ready Ready Preview, Comment, Open in v0 May 30, 2026 7:23pm
shelve-lp Error Error May 30, 2026 7:23pm
shelve-vault Error Error May 30, 2026 7:23pm

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 30, 2026

Thank you for following the naming conventions! 🙏

@github-actions github-actions Bot added the feature New feature or request label May 30, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

Warning

Review limit reached

@HugoRCD, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 46 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ea082f06-a12d-4e7a-9056-4b822197a710

📥 Commits

Reviewing files that changed from the base of the PR and between f103ced and 2b7dcb8.

📒 Files selected for processing (2)
  • packages/cli/src/services/sync.ts
  • packages/cli/src/utils/prompt.ts
📝 Walkthrough

Walkthrough

Sync policies enable fine-grained control over push/pull conflict resolution between local and Shelve-hosted environments, including source-of-truth selection, conflict modes (overwrite/skip/fail/prompt), protected environments on the server, and pull-mode configuration (replace/merge). The implementation spans type definitions, database persistence, server authorization, CLI utilities, service orchestration, and three new/updated commands (diff, push, pull, sync).

Changes

Sync Policies Feature

Layer / File(s) Summary
Sync policy type system and utilities
packages/types/src/Sync.ts, packages/types/src/Project.ts, packages/types/src/Cli.ts, packages/types/schema.json
Defines SourceOfTruth, OnPushConflict, PullMode, SyncPolicy, ShelveSyncConfig, and ResolvedSyncPolicy types. Implements resolveSyncPolicy, mergeSyncPolicies, diffEnvVars, filterVarsByKeys, excludeVarsByKeys, and mergeEnvVarsForPull utility functions. Extends Project, ProjectUpdateInput, and ShelveConfig to include optional syncPolicy fields.
Database migration and project schema
apps/shelve/server/db/migrations/postgresql/0007_project_sync_policy.sql, apps/shelve/server/db/migrations/postgresql/meta/_journal.json, apps/shelve/server/db/schema.ts, apps/shelve/app/utils/zod/project.ts
Adds syncPolicy JSONB column to projects table. Updates Drizzle schema and PostgreSQL migration journal. Extends baseProjectSchema with optional syncPolicy field validated by shelveSyncConfigSchema.
Server-side sync policy authorization
apps/shelve/server/utils/sync-policy.ts
New utility module with getEnvironmentName (404 on missing), assertPushAllowedForEnvironment (403 on allowPush: false), and assertPushAllowedForEnvironmentIds (batch validation) functions used to block protected-environment pushes.
Server API endpoints with sync policy support
apps/shelve/server/api/teams/[slug]/projects/[projectId]/index.put.ts, apps/shelve/server/api/teams/[slug]/projects/[projectId]/variables/[variableId]/index.put.ts, apps/shelve/server/api/teams/[slug]/projects/[projectId]/variables/index.post.ts
Project PUT endpoint accepts and validates syncPolicy. Variable POST/PUT endpoints load project and call assertPushAllowedForEnvironmentIds before creating/updating variables.
CLI type definitions and error codes
packages/types/src/Cli.ts, packages/cli/src/utils/error-codes.ts, packages/cli/schema.json
Adds sync field to ShelveConfig, pullMode to CreateEnvFileInput, syncPolicy to PushEnvFileInput, and PushEnvFileResult type. Adds CLI error codes: PUSH_BLOCKED, PULL_BLOCKED, SYNC_CONFLICT, ENV_PROTECTED. Updates CLI JSON schema to include sync and syncPolicy definitions.
CLI sync-policy resolution and assertion utilities
packages/cli/src/utils/sync-policy.ts, packages/cli/src/utils/index.ts
Implements getEffectiveSyncConfig (merge file/server), getResolvedSyncPolicy (resolve per-environment policy with env-var overrides), assertPushAllowed, and assertPullAllowed. Exposes utilities via utils barrel.
SyncService orchestration and EnvService updates
packages/cli/src/services/sync.ts, packages/cli/src/services/index.ts, packages/cli/src/services/env.ts
New SyncService with resolvePolicy, loadSyncContext, preparePushVariables (handles onPushConflict modes including interactive prompt), mergeForPull, and confirmIfRequired methods. EnvService.createEnvFile now respects pullMode; EnvService.pushEnvFile returns PushEnvFileResult.
diff, push, pull, sync CLI commands
packages/cli/src/commands/diff.ts, packages/cli/src/commands/push.ts, packages/cli/src/commands/pull.ts, packages/cli/src/commands/sync.ts, packages/cli/src/index.ts
New diff command shows local vs remote comparison (read-only). push command resolves sync policy, enforces permissions, prepares variables, and reports skipped/conflict keys. pull command merges remote variables per pullMode and writes env file. New sync command selects push or pull based on sourceOfTruth and supports --dry-run/--yes/--env. Commands wired into CLI index.
Project settings UI for protected environments
apps/shelve/app/pages/[teamSlug]/projects/[projectId]/index/settings.vue
Adds protectedEnvironmentsTextInput ref, a watch to sync UI text with project.syncPolicy.protectedEnvironments, and an input field that parses comma-separated names on blur.
Zod validation for sync policy
apps/shelve/app/utils/zod/sync-policy.ts, apps/shelve/app/utils/zod/project.ts
Adds strict Zod schemas for sync-policy fields and top-level ShelveSyncConfig and plugs the schema into project validation.
Documentation and release notes
.changeset/sync-policies-cli.md, AGENTS.md, apps/lp/content/docs/3.cli/*, apps/lp/skills/shelve/*, docs/agents/*
Adds comprehensive docs for sync policies, new CLI commands, error codes, merge/pull semantics, protected environments, and updates agent-skill references to a single shelve skill.
Sync policy test suite
packages/cli/test/sync-policy.test.ts
Vitest coverage for resolveSyncPolicy defaults and overrides, mergeSyncPolicies precedence, env-var driven allowPush override, diffEnvVars classification, and mergeEnvVarsForPull.

Sequence Diagrams

sequenceDiagram
  participant User
  participant diff as shelve diff
  participant sync as shelve sync
  participant SyncSvc as SyncService
  participant EnvSvc as EnvService
  participant Shelve as Shelve API

  User->>diff: shelve diff --env prod
  diff->>SyncSvc: loadSyncContext()
  SyncSvc->>EnvSvc: Load local .env file
  SyncSvc->>Shelve: Fetch remote variables
  SyncSvc->>SyncSvc: diffEnvVars() → onlyLocal, onlyRemote, changed, unchanged
  SyncSvc-->>diff: SyncContext with diff result
  diff-->>User: Display local vs remote (read-only)

  User->>sync: shelve sync --env prod
  sync->>SyncSvc: loadSyncContext()
  SyncSvc-->>sync: SyncContext with sourceOfTruth policy
  alt policy.sourceOfTruth === 'local'
    sync->>SyncSvc: preparePushVariables(policy.onPushConflict)
    SyncSvc-->>sync: variables + skipped/conflict keys
    sync->>EnvSvc: pushEnvFile(variables, syncPolicy)
    EnvSvc->>Shelve: Push variables
    Shelve-->>EnvSvc: success/protected-env blocks
    EnvSvc-->>sync: PushEnvFileResult
    sync-->>User: Summary with pushed count, skipped/conflict keys
  else policy.sourceOfTruth === 'remote'
    sync->>SyncSvc: mergeForPull(policy.pullMode)
    SyncSvc-->>sync: merged variables
    sync->>EnvSvc: createEnvFile(variables, pullMode)
    EnvSvc-->>sync: written file path
    sync-->>User: Summary with variable count, keys written
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • HugoRCD/shelve#751: Modifies push/pull command confirmation flows for agent/non-interactive mode; this PR extends those same commands with sync-policy resolution and enforcement.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely summarizes the main changes: introducing sync policies, two new CLI commands (diff/sync), and unifying the agent skill into a single published skill.
Description check ✅ Passed The PR description is well-structured with a clear summary of major changes, server-side implementation details, agent skill consolidation, and a detailed test plan checklist. It covers the required context effectively.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sync-policies-unified-skill

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 30, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@shelve/cli@752

commit: 2b7dcb8

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/lp/content/docs/3.cli/11.troubleshooting.md (1)

123-123: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use singular wording for the unified catalog entry.

“Install the published skills” conflicts with the new single-skill model (shelve). Rename to “Install the published skill” for consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/lp/content/docs/3.cli/11.troubleshooting.md` at line 123, Change the
heading/text "Install the published skills:" to the singular form "Install the
published skill:" to match the new single-skill model; locate the phrase in the
document (appearing as the installation instruction for the unified catalog) and
update the wording accordingly so the catalog entry uses singular "skill"
instead of plural "skills."
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/lp/skills/shelve/cli-commands.md`:
- Line 141: The example JSON in cli-commands.md is inconsistent: it shows
"action": "pull" but uses a contradictory "pushed": true field; update the
example so the output reflects a pull operation (e.g., remove or replace
"pushed" with a neutral field like "direction": "pull" or use a pull-specific
flag) — locate the JSON example containing "action": "pull" and "pushed" and
change "pushed": true to a neutral or pull-accurate value (for example
"direction": "pull" or "pushed": false) so the example is semantically
consistent.

In `@apps/shelve/app/pages/`[teamSlug]/projects/[projectId]/index/settings.vue:
- Around line 16-28: The computed protectedEnvironmentsText currently normalizes
the value on every input (get + dead set) causing commas to disappear
mid-typing; replace it with a plain ref (e.g., protectedEnvironmentsTextInput)
that you initialize from
project.value?.syncPolicy?.protectedEnvironments.join(', ') and update directly
on input so the raw string (including trailing comma/whitespace) remains
displayed; remove the empty set on the computed; then call the existing parsing
logic (reuse onProtectedEnvironmentsInput or move its parsing code) from an
onBlur handler to parse the ref into names and set
project.value.syncPolicy.protectedEnvironments (names.length ? names :
undefined); keep onProtectedEnvironmentsInput as the parsing/updating routine or
rename it and call it from blur, and ensure UInput binds to the new ref instead
of the computed.

In `@apps/shelve/server/api/teams/`[slug]/projects/[projectId]/index.put.ts:
- Around line 4-8: syncPolicySchema currently permits arbitrary nested values
(uses z.unknown()) which doesn't match the canonical types; replace it by
importing/using the shared Zod validators for ShelveSyncConfig and SyncPolicy
(or define a strict SyncPolicy schema) and use those instead of z.record(...
z.unknown()); ensure you reject unknown keys (use .strict()) and validate typed
fields like allowPush?: boolean, allowPull?: boolean, etc., then wire that
schema into the project update handler in place of syncPolicySchema so default
and environments are validated against the real SyncPolicy shape and
protectedEnvironments remains an array of non-empty strings.

In `@apps/shelve/server/utils/sync-policy.ts`:
- Around line 30-39: Replace the serial getEnvironmentName calls with a single
batched query: use an inArray-based fetch (import inArray) to load all
environments for the given teamId in one query (e.g. a new
helper/getEnvironmentsByIds or inline query that filters id
inArray(environmentIds) and teamId) then iterate the returned rows and call
assertPushAllowedForEnvironment(name, syncPolicy) for each environment; ensure
missing IDs are handled consistently with previous behavior and that inArray is
imported where used.

In `@packages/cli/schema.json`:
- Around line 64-71: The syncPolicy JSON schema properties (sourceOfTruth,
onPushConflict, pullMode, allowPush, allowPull, requireConfirmation) lack
description fields; update the schema to add a descriptive "description" for
each property explaining its purpose and valid values (e.g., sourceOfTruth:
explain "remote" vs "local"; onPushConflict: list options and behavior;
pullMode: describe "replace" vs "merge"; allowPush/allowPull: booleans control
push/pull capabilities; requireConfirmation: when user confirmation is required)
so IDEs can surface helpful tooltips and autocomplete guidance.

In `@packages/cli/src/commands/diff.ts`:
- Line 98: The final conditional is redundant because isJson() already causes an
early return earlier; remove the unnecessary if (!isJson()) check and call
cliSuccess(undefined, 'Diff complete') directly (or eliminate the call only if
that code path is unreachable by design) — update the code around the Diff
completion to use cliSuccess unconditionally instead of wrapping it in isJson()
check; reference functions: isJson() and cliSuccess.
- Around line 88-95: The lookup mismatch occurs because localMap and remoteMap
are created with uppercased keys but diff.changed contains original-cased keys
when autoUppercase is false; update the lookup so it uses the same casing rule
as map construction: either build maps using the original key casing from
syncContext.local/remote (remove toUpperCase when creating localMap/remoteMap)
or, if maps must be uppercased, change the lookup in the loop to use
key.toUpperCase() (i.e., for (const key of diff.changed) { lines.push(`  ${key}:
${localMap.get(key.toUpperCase()) ?? '?'} → ${remoteMap.get(key.toUpperCase())
?? '?'}`) }); make the change in the block that constructs localMap/remoteMap
and in the loop that references diff.changed so both use identical casing logic.

In `@packages/cli/src/commands/push.ts`:
- Around line 89-90: The code mutates the result object returned from
pushEnvFile by adding skippedKeys and conflictKeys; instead modify pushEnvFile
to include skippedKeys and conflictKeys on its returned value (or have it return
a new structured object) and update the call site that currently assigns result
to use the new shape, or alternatively construct a new output object (e.g., {
...result, skippedKeys, conflictKeys }) instead of mutating result; locate
pushEnvFile and the variables result, skippedKeys, and conflictKeys to implement
the change and update any consumers accordingly.

In `@packages/cli/src/commands/sync.ts`:
- Around line 89-96: Extract the duplicated non-interactive confirmation check
into a shared helper (e.g., assertConfirmationAllowed or
ensureInteractiveOrConfirmed) and replace the inline block in sync.ts (the if
using confirmChanges, policy.requireConfirmation, skipConfirm,
isNonInteractive() that throws CliError) with a call to that helper; implement
the helper to accept the same flags/values (confirmChanges,
policy.requireConfirmation, skipConfirm) and throw the identical CliError with
the same message/code/suggestion when non-interactive confirmation is required,
then reuse that helper from push.ts and sync.ts to remove duplication and ensure
consistent behavior.

In `@packages/cli/src/services/sync.ts`:
- Around line 68-70: The current early return in the function in sync.ts hides
overwrite details by returning conflictKeys: [] when policy.onPushConflict ===
'overwrite'; change the logic so that you only return conflictKeys: [] when
conflictKeys.length === 0, but if conflicts exist and policy.onPushConflict ===
'overwrite' return the actual conflictKeys (alongside variables and skippedKeys)
so callers can detect which keys were overwritten; update the return at the
branch referencing variables, skippedKeys and conflictKeys accordingly.
- Around line 38-56: loadSyncContext currently takes five positional parameters
(project, environmentId, environmentName, slug, autoUppercase) which exceeds the
max-params lint rule; change its signature to accept a single options object
(e.g. { project, environmentId, environmentName, slug, autoUppercase }) and
update internal references to use options.project etc., keeping return type
SyncContext intact and still calling resolvePolicy(options.environmentName,
options.project, config.sync). Then update every call site that invokes
loadSyncContext in diff.ts, push.ts, pull.ts, and sync.ts to pass an object with
those named properties instead of positional arguments so the behavior remains
identical.
- Around line 130-138: confirmIfRequired currently discards the result of await
askBoolean(message) so a Ctrl+C cancel (the `@clack/prompts` cancel symbol) can
slip through; change confirmIfRequired to capture the response (const response =
await askBoolean(message)) and then if response is the cancel symbol (use
isCancel(response)) or response === false, call cliCancel() or throw to abort
the sync; update references to askBoolean, confirmIfRequired, isCancel, and
cliCancel accordingly so cancellation always stops the flow.

In `@packages/cli/test/sync-policy.test.ts`:
- Around line 10-32: Add tests to cover pullMode: 'merge' and allowPull blocking
in the resolveSyncPolicy suite: create one test calling
resolveSyncPolicy('development', { default: { pullMode: 'merge' } }) and assert
policy.pullMode === 'merge', and another test calling
resolveSyncPolicy('development', { default: { allowPull: false } }) and assert
policy.allowPull === false; place these alongside the existing tests in the
describe('resolveSyncPolicy') block so behavior for default overrides and
blocking is fully covered.

---

Outside diff comments:
In `@apps/lp/content/docs/3.cli/11.troubleshooting.md`:
- Line 123: Change the heading/text "Install the published skills:" to the
singular form "Install the published skill:" to match the new single-skill
model; locate the phrase in the document (appearing as the installation
instruction for the unified catalog) and update the wording accordingly so the
catalog entry uses singular "skill" instead of plural "skills."
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fad56517-c7c2-4c91-8f5b-12601de87948

📥 Commits

Reviewing files that changed from the base of the PR and between f9a0848 and 5530823.

📒 Files selected for processing (43)
  • .changeset/sync-policies-cli.md
  • AGENTS.md
  • apps/lp/content/docs/3.cli/10.agents-automation.md
  • apps/lp/content/docs/3.cli/11.troubleshooting.md
  • apps/lp/content/docs/3.cli/12.sync-policies.md
  • apps/lp/content/docs/3.cli/2.index.md
  • apps/lp/content/docs/3.cli/5.push-pull.md
  • apps/lp/content/docs/3.cli/7.config.md
  • apps/lp/nuxt.config.ts
  • apps/lp/skills/shelve/SKILL.md
  • apps/lp/skills/shelve/agent-workflows.md
  • apps/lp/skills/shelve/cli-commands.md
  • apps/lp/skills/shelve/platform.md
  • apps/lp/skills/shelve/sync-policies.md
  • apps/shelve/app/pages/[teamSlug]/projects/[projectId]/index/settings.vue
  • apps/shelve/app/utils/zod/project.ts
  • apps/shelve/server/api/teams/[slug]/projects/[projectId]/index.put.ts
  • apps/shelve/server/api/teams/[slug]/projects/[projectId]/variables/[variableId]/index.put.ts
  • apps/shelve/server/api/teams/[slug]/projects/[projectId]/variables/index.post.ts
  • apps/shelve/server/db/migrations/postgresql/0007_project_sync_policy.sql
  • apps/shelve/server/db/migrations/postgresql/meta/_journal.json
  • apps/shelve/server/db/schema.ts
  • apps/shelve/server/utils/sync-policy.ts
  • docs/agents/cli.md
  • docs/agents/docs-links.md
  • packages/cli/schema.json
  • packages/cli/src/commands/diff.ts
  • packages/cli/src/commands/pull.ts
  • packages/cli/src/commands/push.ts
  • packages/cli/src/commands/sync.ts
  • packages/cli/src/index.ts
  • packages/cli/src/services/env.ts
  • packages/cli/src/services/index.ts
  • packages/cli/src/services/sync.ts
  • packages/cli/src/utils/error-codes.ts
  • packages/cli/src/utils/index.ts
  • packages/cli/src/utils/sync-policy.ts
  • packages/cli/test/sync-policy.test.ts
  • packages/types/index.ts
  • packages/types/schema.json
  • packages/types/src/Cli.ts
  • packages/types/src/Project.ts
  • packages/types/src/Sync.ts

Comment thread apps/lp/skills/shelve/cli-commands.md Outdated
Comment thread apps/shelve/app/pages/[teamSlug]/projects/[projectId]/index/settings.vue Outdated
Comment thread apps/shelve/server/api/teams/[slug]/projects/[projectId]/index.put.ts Outdated
Comment thread apps/shelve/server/utils/sync-policy.ts
Comment thread packages/cli/schema.json
Comment thread packages/cli/src/commands/sync.ts Outdated
Comment thread packages/cli/src/services/sync.ts Outdated
Comment thread packages/cli/src/services/sync.ts Outdated
Comment thread packages/cli/src/services/sync.ts
Comment thread packages/cli/test/sync-policy.test.ts
Use camelCase "syncPolicy" in migration to match Drizzle schema (fixes e2e
CI failures). Harden CLI push/sync/diff, server sync-policy batch checks, Zod
validation, settings UI, and agent skill JSON examples.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/cli/src/services/sync.ts (1)

135-143: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle cancellation in confirmIfRequired.

The result of askBoolean(message) is discarded. If the user presses Ctrl+C, @clack/prompts returns a cancel symbol that askBoolean may not handle internally, allowing execution to continue when it should abort.

🐛 Proposed fix
   static async confirmIfRequired(
     policy: ResolvedSyncPolicy,
     confirmChanges: boolean,
     skipConfirm: boolean,
     message: string,
   ): Promise<void> {
     const needsConfirm = (confirmChanges || policy.requireConfirmation) && !skipConfirm
-    if (needsConfirm) await askBoolean(message)
+    if (needsConfirm) {
+      const response = await askBoolean(message)
+      if (!response) cliCancel('Cancelled.')
+    }
   }

Run the following script to verify how askBoolean handles cancellation:

#!/bin/bash
# Check askBoolean implementation to see if it handles cancel internally
rg -nA 20 'export.*function askBoolean|export async function askBoolean' packages/cli/src
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/services/sync.ts` around lines 135 - 143, confirmIfRequired
currently discards askBoolean(message) result so a Ctrl+C cancel can slip
through; update confirmIfRequired to await and capture the result (e.g. const
result = await askBoolean(message)), then detect cancellation using the cancel
symbol helper from `@clack/prompts` (or compare against the cancel constant) and
throw/reject (or call process.exit(1)) to abort when cancelled; otherwise
continue only when the boolean result indicates confirmation. Reference the
confirmIfRequired function and the askBoolean call when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@packages/cli/src/services/sync.ts`:
- Around line 135-143: confirmIfRequired currently discards askBoolean(message)
result so a Ctrl+C cancel can slip through; update confirmIfRequired to await
and capture the result (e.g. const result = await askBoolean(message)), then
detect cancellation using the cancel symbol helper from `@clack/prompts` (or
compare against the cancel constant) and throw/reject (or call process.exit(1))
to abort when cancelled; otherwise continue only when the boolean result
indicates confirmation. Reference the confirmIfRequired function and the
askBoolean call when making this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ead843db-d9fb-41f1-8e72-c85d3be15f98

📥 Commits

Reviewing files that changed from the base of the PR and between 5530823 and f103ced.

📒 Files selected for processing (16)
  • apps/lp/skills/shelve/cli-commands.md
  • apps/shelve/app/pages/[teamSlug]/projects/[projectId]/index/settings.vue
  • apps/shelve/app/utils/zod/project.ts
  • apps/shelve/app/utils/zod/sync-policy.ts
  • apps/shelve/server/api/teams/[slug]/projects/[projectId]/index.put.ts
  • apps/shelve/server/db/migrations/postgresql/0007_project_sync_policy.sql
  • apps/shelve/server/utils/sync-policy.ts
  • packages/cli/schema.json
  • packages/cli/src/commands/diff.ts
  • packages/cli/src/commands/pull.ts
  • packages/cli/src/commands/push.ts
  • packages/cli/src/commands/sync.ts
  • packages/cli/src/services/sync.ts
  • packages/cli/src/utils/confirmation.ts
  • packages/cli/src/utils/index.ts
  • packages/cli/test/sync-policy.test.ts

HugoRCD added 2 commits May 30, 2026 20:22
Refactor confirmation logic to streamline user prompts. The confirmation check now returns early if confirmation is not needed, and the cancellation handling is improved to ensure consistent user experience.
@HugoRCD HugoRCD merged commit 787cf07 into main May 30, 2026
13 of 15 checks passed
@HugoRCD HugoRCD deleted the feat/sync-policies-unified-skill branch May 30, 2026 19:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant